package com.srv.common;

import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.srv.persistence.PersistenceException;
import com.srv.util.DataBaseUtil;
import com.srv.util.StringUtil;


public class DataSourceManager {

	private static Log log = LogFactory.getLog(DataSourceManager.class);
	
	private static Map<String, DataSource> map = new HashMap<String, DataSource>(); 
	
	private static Map<Long, Integer> mappingCache = new HashMap<Long, Integer>(10);
	
	private static final String MAPPING_TABLE_DATASOURCE_NAME = "srv_def";
	
	private static final String PREFIX_DATASOURCE_NAME = "srv_database_";
	
	private static final String MAPPING_SQL = "SELECT * FROM PID_DSID_MAPPING WHERE PID = ?";
	
	private static final String INSERT_MAPPING_SQL = "INSERT INTO PID_DSID_MAPPING (PID, DSID) VALUES (?, ?)";
	
	private static final IDataSourceSelectStrategy selectStrategy = new DefaultSelectStrategy();
	
	private static DataSourceManager instance = new DataSourceManager();
	
	private DataSourceManager(){
	}
	
	public static DataSourceManager get(){
		return instance;
	}
	
	public int createDataSourceMapping(long pid) throws PersistenceException{
		//check the mapping
		int dsNum = getDataSourceMapping(pid);
		if(dsNum != -1){
			throw new PersistenceException("The datasource mapping is already exists");
		}
		//create datasource Number
		dsNum = selectStrategy.getDateSourceNumber();
		
		//save to mapping table
		Connection conn = null;
		PreparedStatement st = null;
		try {
			conn = getDataSource(MAPPING_TABLE_DATASOURCE_NAME).getConnection();
			st = conn.prepareStatement(INSERT_MAPPING_SQL);
			st.setLong(1, pid);
			st.setInt(2, dsNum);
			int flag = st.executeUpdate();
			if(flag == 1){
				mappingCache.put(pid, dsNum);
				return dsNum;
			}
		} catch (SQLException e) {
			e.printStackTrace();
			throw new PersistenceException(e.getMessage());
		} finally{
			DataBaseUtil.closeStatment(st);
			DataBaseUtil.closeConnection(conn);
		}
		return -1;
	}
	
	public int getDataSourceMapping(long partitionID) throws PersistenceException{
		//check the cache
		if(mappingCache.containsKey(partitionID)){
			return mappingCache.get(partitionID);
		}
		
		Connection conn = null;
		PreparedStatement st = null;
		ResultSet rs = null;
		int datasourceNum = -1;
		try {
			conn = getDataSource(MAPPING_TABLE_DATASOURCE_NAME).getConnection();
			st = conn.prepareStatement(MAPPING_SQL);
			st.setLong(1, partitionID);
			rs = st.executeQuery();
			if(rs.next()){
				datasourceNum = rs.getInt("DSID");
			}
			if(datasourceNum != -1){
				//put the mappingDatasourceName to cache
				mappingCache.put(partitionID, datasourceNum);
			}
		} catch (SQLException e) {
			e.printStackTrace();
			throw new PersistenceException(e.getMessage());
		} finally{
			DataBaseUtil.closeResultSet(rs);
			DataBaseUtil.closeStatment(st);
			DataBaseUtil.closeConnection(conn);
		}
		return datasourceNum;
	}
	
	public String getDataSourceNameByPID(long partitionID) throws PersistenceException{
		int dsNum = getDataSourceMapping(partitionID);
		if(dsNum == -1){
			log.error("datasource number not found");
			return null;
		}
		String mappingDatasourceName = PREFIX_DATASOURCE_NAME + dsNum;
		return mappingDatasourceName;
	}
	
	public DataSource getDataSource(long partitionID) throws PersistenceException{
		String dsName = getDataSourceNameByPID(partitionID);
		if(dsName == null){
			throw new PersistenceException("datasource name not found");
		}
		return getDataSource(dsName);
	}

	public DataSource getDataSource(String dataSourceName) throws PersistenceException {
		if(map.containsKey(dataSourceName)){
			return map.get(dataSourceName);
		}
		DataSourceProps dsProps = null;
		try {
			dsProps = new DataSourceProps(dataSourceName);
		} catch (PersistenceException e) {
			log.error("Append the datasource configure failed.");
			throw e;
		}
		DataSource returnDs;
		try {
			returnDs = createDataSource(dsProps);
		} catch (PropertyVetoException e) {
			e.printStackTrace();
			throw new PersistenceException(e.getMessage());
		}
		return returnDs;
	}
	
	public DataSource createDataSource(DataSourceProps dsProps) throws PropertyVetoException{
		DataSource returnDs = null;

        ComboPooledDataSource c3p0Ds = new ComboPooledDataSource();

        c3p0Ds.setDataSourceName(dsProps.getName());
        try {
            c3p0Ds.setDriverClass(dsProps.getDriverClassName());
        } catch (PropertyVetoException e) {
        	log.error("c3p0 load driver class error.");
            throw e;
        }

        c3p0Ds.setJdbcUrl(dsProps.getUrl());

        //set the password and username of the db account
        c3p0Ds.setUser(dsProps.getUserName());
      
        c3p0Ds.setPassword(dsProps.getPassword());

        if (!StringUtil.isEmpty(dsProps.getValidateSQL())) {
            c3p0Ds.setPreferredTestQuery(dsProps.getValidateSQL());
        }

        c3p0Ds.setMinPoolSize(dsProps.getInitialSize());
        c3p0Ds.setMaxPoolSize(dsProps.getMaxActive());
        //c3p0Ds.setMaxIdleTime(dsProps.getMaxIdle());
        c3p0Ds.setCheckoutTimeout((int) (dsProps.getMaxWait()));
        c3p0Ds.setMaxStatementsPerConnection(dsProps.getMaxStatements());

        returnDs = c3p0Ds;
        

        map.put(dsProps.getName(), returnDs);

        return returnDs;
	}

}
